/*
 *    Copyright © OpenAtom Foundation.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package com.inspur.edp.bef.core.determination.builtinimpls.predicate;

import com.inspur.edp.cef.entity.changeset.ChangeType;
import com.inspur.edp.cef.entity.changeset.IChangeDetail;
import com.inspur.edp.cef.entity.changeset.Util;
import java.util.List;
import java.util.Objects;

public class ExecutePredicateFactory {

  private static ExecutePredicate emptyP = new ExecutePredicate() {
    @Override
    public boolean test(IChangeDetail change) {
      return false;
    }
  };

  public static ExecutePredicate getAggregatePredicate(ExecutePredicate... predicates) {
//    Objects.requireNonNull(predicates, "predicates");
    if (predicates == null || predicates.length == 0) {
      return emptyP;
    }
    if (predicates.length == 1) {
      return predicates[0];
    }
    return new AggregatePredicate(predicates);
  }

  private static AddPredicate addP = new AddPredicate();

  public static ExecutePredicate getAddPredicate() {
    return addP;
  }

  private static DeletePredicate deleteP = new DeletePredicate();

  public static ExecutePredicate getDeletePredicate() {
    return deleteP;
  }

  private static NoneElementModifyPredicate modifyP = new NoneElementModifyPredicate();

  public static ExecutePredicate getModifyPredicate(List<String> elements) {
    return elements == null || elements.isEmpty() ? modifyP : new ModifyPredicate(elements);
  }

  public static ExecutePredicate getChildAddPredicate(String childCode) {
    return new ChildAddPredicate(childCode);
  }

  public static ExecutePredicate getChildDeletePredicate(String childCode) {
    return new ChildDeletePredicate(childCode);
  }

  public static ExecutePredicate getChildModifyPredicate(String childCode,
      List<String> childElements) {
    return new ChildModifyPredicate(childCode, childElements);
  }

  private static class AggregatePredicate implements ExecutePredicate {

    private final ExecutePredicate[] predicates;

    public AggregatePredicate(ExecutePredicate[] predicates) {
      Objects.requireNonNull(predicates, "predicates");
      this.predicates = predicates;
    }

    @Override
    public boolean test(IChangeDetail change) {
      for (ExecutePredicate item : predicates) {
        if (item.test(change)) {
          return true;
        }
      }
      return false;
    }
  }

  private static class AddPredicate implements ExecutePredicate {

    @Override
    public boolean test(IChangeDetail change) {
      return change != null && change.getChangeType() == ChangeType.Added;
    }
  }

  private static class DeletePredicate implements ExecutePredicate {

    @Override
    public boolean test(IChangeDetail change) {
      return change != null && change.getChangeType() == ChangeType.Deleted;
    }
  }

  private static class NoneElementModifyPredicate implements ExecutePredicate {

    @Override
    public boolean test(IChangeDetail change) {
      return change != null && change.getChangeType() == ChangeType.Modify;
    }
  }

  private static class ModifyPredicate implements ExecutePredicate {

    private List<String> elements;

    public ModifyPredicate(List<String> elements) {
      this.elements = elements;
    }

    @Override
    public boolean test(IChangeDetail change) {
      if (change == null || change.getChangeType() != ChangeType.Modify) {
        return false;
      }
      if (elements == null || elements.isEmpty()) {
        return true;
      } else {
        return Util.containsAny(change, elements);
      }
    }
  }

  private static class ChildAddPredicate implements ExecutePredicate {

    private final String code;

    public ChildAddPredicate(String code) {
      this.code = code;
    }

    @Override
    public boolean test(IChangeDetail change) {
      if (change == null) {
        return false;
      }
      return containsChildChangeType(change, code, ChangeType.Added);
    }
  }

  private static class ChildDeletePredicate implements ExecutePredicate {

    private final String code;

    public ChildDeletePredicate(String code) {
      this.code = code;
    }

    @Override
    public boolean test(IChangeDetail change) {
      if (change == null) {
        return false;
      }
      return containsChildChangeType(change, code, ChangeType.Deleted);
    }
  }

  private static class ChildModifyPredicate implements ExecutePredicate {

    private final String code;
    private final List<String> elements;

    public ChildModifyPredicate(String code, List<String> elements) {
      Objects.requireNonNull(code, "code");
//      Objects.requireNonNull(elements, "elements");

      this.code = code;
      this.elements = elements;
    }

    @Override
    public boolean test(IChangeDetail change) {
      if (change == null) {
        return false;
      }
      for (IChangeDetail childChange : Util.getChildChanges(change, code)) {
        if (childChange.getChangeType() == ChangeType.Modify && (elements == null || Util
            .containsAny(childChange, elements))) {
          return true;
        }
      }
      return false;
    }
  }

  private static boolean containsChildChangeType(IChangeDetail change, String code,
      ChangeType type) {
    for (IChangeDetail childChange : Util.getChildChanges(change, code)) {
      if (childChange.getChangeType() == type) {
        return true;
      }
    }
    return false;
  }
}
